[comment {-*- flibs -*- doctools manpage}]
[manpage_begin flibs/funit n 1.1]
[copyright {2006 Arjen Markus <arjenmarkus@sourceforge.net>}]
[moddesc flibs]
[titledesc {Unit testing}]

[description]

[emph JUnit] is a well-known facility for defining and running unit
tests in Java programs. The [emph funit] framework was inspired by
that facility. It is not as good-looking as JUnit, by no means:
[list_begin bullet]
[bullet]
It has no graphical user-interface

[bullet]
As Fortran does not allow introspection, the test routines can not
be detected automatically, instead as a programmer you need to set up a
high-level routine yourself that collects all the unit tests.

[bullet]
A runtime error, like division by zero, may lead to a termination of
the program. There is no (portable) way to catch these. Instead, the
framework relies on a batch file or shell script to repeatedly start the
program until all tests are run.

[list_end]

Despite these limitations, [emph funit] can be a great help:

[list_begin bullet]
[bullet]
The code to test the various components (subroutines, functions, tasks
consisting of several program units) can be combined with the program
itself, without interfering with the ordinary code.
[nl]
This is achieved by defining a single routine (test_all, say) that runs
all the unit tests and that is called via the provided routine
[emph runtests]:

[example {
program myprog
    ...
!
! The routine runtests will check if unit tests are requested
! If not, it will return immediately. This way we make sure
! the unit tests remain part of the program.
!
! The routine test_all runs all unit tests
! (see the dataproc_testing module)
!
    call runtests( test_all )
!
! Ordinary processing
!
    ...

end program
}]

The routine runtests checks if there is a file "funit.run". If there is
such a file, it will run the given subroutine. Otherwise it will return
and the rest of the program is executed.

[bullet]
Because the test code is incorporated in the program itself, it is less
likely that they evolve independently: changes in the argument lists of
the subroutines and functions may lead to compile errors in the test
code.

[bullet]
There is no need to set up a whole new program for testing portions of
the program.

[list_end]

The source file "test_funit.f90" illustrates how to use the funit
framework:

[list_begin bullet]
[bullet]
The main program calls the routine "runtests" and passes it the argument
"test_all", a routine defined in a module called "dataproc_testing".

[bullet]
The routine "test_all" consists of nothing but calls to the generic
routine "test":

[example {
subroutine test_all

    call test( test_no_file, "Read non-existent file" )
    call test( test_empty_file, "Read an empty file" )
    call test( test_invalid_file, "Read an invalid file" )
    call test( test_ordinary_file, "Read an ordinary file" )

end subroutine test_all
}]

[bullet]
The module includes a source file "funit_test.f90" which defines the
generic subroutine "test", thereby creating a copy [emph local] to
the module. It should also define two auxiliary routines,
[emph prolog] and [emph epilog] that can be used for code that is
common to all unit tests.

[bullet]
The generic routine "test" checks whether a particular unit
test needs to be run (via the test number) and then runs the subroutine
that was passed as one of its arguments. One such routine looks like
this:

[example {
subroutine test_no_file

    integer :: nodata
    real    :: vmean, vmin, vmax

    call funit_remove_file( 'no_such_file' )
    call write_name( 'no_such_file' )

    call open_files
    call process_data( nodata, vmean, vmax, vmin )

    call assert_true( nodata == 0, "No data read" )

end subroutine test_no_file
}]

The assertion is used to check that the result is as expected.

[bullet]
The program contains some deliberate errors and the resulting
log file looks like this":

[example {
Test: Read non-existent file
Test: Read an empty file
Test: Read an invalid file
forrtl: severe (59): list-directed I/O syntax error, unit 11, file c:\arjen\flibs\tests\funit\invalid_file

Image              PC        Routine            Line        Source
test_funit.exe     004151B9  Unknown               Unknown  Unknown
test_funit.exe     00415017  Unknown               Unknown  Unknown
test_funit.exe     004141F4  Unknown               Unknown  Unknown
test_funit.exe     00414629  Unknown               Unknown  Unknown
test_funit.exe     00409C05  Unknown               Unknown  Unknown
test_funit.exe     004095FB  Unknown               Unknown  Unknown
test_funit.exe     0040144B  Unknown               Unknown  Unknown
test_funit.exe     00401FE9  Unknown               Unknown  Unknown
test_funit.exe     00401A2C  Unknown               Unknown  Unknown
test_funit.exe     00401BB3  Unknown               Unknown  Unknown
test_funit.exe     0040294A  Unknown               Unknown  Unknown
test_funit.exe     0040232E  Unknown               Unknown  Unknown
test_funit.exe     0044A1E9  Unknown               Unknown  Unknown
test_funit.exe     00433519  Unknown               Unknown  Unknown
kernel32.dll       7C816D4F  Unknown               Unknown  Unknown

Incrementally linked image--PC correlation disabled.
Test: Read an ordinary file
Number of failed assertions:                    0
Number of runs needed to complete the tests:    3
}]

[list_end]

The program is run via one of the following files:

[list_begin definitions]

[call runtests.bat]
A batch file for use under MS Windows
[call runtests.sh]
A Bourne shell script for use under UNIX/Linux or similar systems, like
Cygwin or Mingw.
[call runtests.tcl]
A Tcl program that presents a simple graphical user-interface

[list_end]

[section ROUTINES]
The module funit contains the following subroutines and functions:

[list_begin definitions]

[call [cmd "call runtests( testproc )"]]
Routine to start the unit tests. It checks if the file "funit.run"
exists. If so, it will call the subroutine [emph testproc] that was
passed. Otherwise it will simply return, so that the ordinary program
execution may continue.
[nl]
If the subroutine testproc returns, the program stops.

[list_begin arg]
[arg_def "subroutine" testproc]
Subroutine that calls the individual test routines. It takes no
arguments. It wil generally exist of a series of calls to the
routine [emph test] - see below.
[list_end]


[call [cmd "call test( proc, text )"]]
Routine to run the individual unit test routine (emph proc). It decides
if the test has not run yet and if so, the test routine is called.
Otherwise it is skipped.
[nl]
[emph test] takes care of all administrative details.
[nl]
Note: to make it possible to use [emph private] unit test routines,
the source code of this subroutine is kept in a separate file,
[emph funit_test.f90] that should be included in an appropriate
place in the program's sources. This way, you can make it a private
routine in each module. The only public access to the unit testing
routines is then via the subroutine [emph testproc] that is passed to
[emph runtests].

[list_begin arg]
[arg_def "subroutine" proc]
Subroutine that implements an individual unit test. It takes no
arguments. Within each such subroutine the complete unit test is run.

[arg_def "character(len=*), intent(in)" text]
Text describing the particular unit test. It is printed in the log
file.
[list_end]


[call [cmd "call assert_true( cond, text )"]]
Routine to check that a condition is true. If not, a message is printed
in the log file and the number of failures is increased.

[list_begin arg]
[arg_def "logical" cond]
The condition to be checked

[arg_def "character(len=*), intent(in)" text]
Text describing the condition
[list_end]


[call [cmd "exists = funit_file_exists( filename )"]]
Logical function to check that a particular file exists

[list_begin arg]
[arg_def "character(len=*), intent(in)" filename]
Name of the file to be checked
[list_end]


[call [cmd "call funit_get_lun( lun )"]]
Subroutine to get a free LU-number

[list_begin arg]
[arg_def "integer, intent(out)" lun]
Next free LU-number
[list_end]


[call [cmd "call funit_remove_file( filename )"]]
Subroutine to remove (delete) a file

[list_begin arg]
[arg_def "character(len=*), intent(in)" filename]
Name of the file to be removed
[list_end]


[call [cmd "call funit_make_empty_file( filename )"]]
Subroutine to make a new, empty file

[list_begin arg]
[arg_def "character(len=*), intent(in)" filename]
Name of the file to be created
[list_end]

[list_end]


[section TODO]
The following things are still left to do:
[list_begin bullet]
[bullet]
Proper inclusion of the routine [emph prolog] and [emph epilog]

[bullet]
Extension of the set of assertion routines

[list_end]
[manpage_end]
